import fs from "fs-extra";
import path from "path";
import { indent } from "../tools/vendor.js";
import { EntryDef, JSONDropFieldRule, JsonDiffValueRule, MacrosDef, StructDef } from "../typings";
import { JsonStructParser } from "./JsonStructParser.js";
import { SvnHolder, adjustSVN } from "./SvnHolder.js";
import { toolchain } from "../tools/toolchain.js";

declare type TGroupInfo = {
    name: string
    members: string[]
}

export class Xml2Ts {
    private static ins: Xml2Ts;
    public static get Instance(): Xml2Ts {
        if(!Xml2Ts.ins) Xml2Ts.ins = new Xml2Ts();
        return Xml2Ts.ins;
    }

    private structPath!: string;
    private gameConfigPath!: string;
    private keywordPath!: string;
    private gameConfigPathsToSync: string[] = [];
    private keywordPathsToSync: string[] = [];

    private constructor() {}

    public async execute(): Promise<void> {
        const temp = path.join(toolchain.options.workspace!, toolchain.options.publish ? toolchain.options.project + '_publish' : toolchain.options.project);
        this.structPath = path.join(temp, 'struct');
        this.gameConfigPath = path.join(temp, 'ts/data');
        this.keywordPath = path.join(temp, 'ts/constants');
        // 更新svn
        await SvnHolder.Instance.checkSVN(!toolchain.options.debug, this.structPath, adjustSVN(toolchain.projCfg.Svn.DesignerSvn) + 'xml');
        await this.initClientSVNs(toolchain.projCfg.Svn.ClientSvn, this.gameConfigPath, this.keywordPath);
        if (toolchain.projCfg.Sync) {
            for (let i = 0, len = toolchain.projCfg.Sync.length; i < len; i++) {
                const gp = path.join(temp, `ts/dataToSync${i}`);
                const kp = path.join(temp, `ts/constantsToSync${i}`);
                this.gameConfigPathsToSync.push(gp);
                this.keywordPathsToSync.push(kp);
                await this.initClientSVNs(toolchain.projCfg.Sync[i], gp, kp);
            }
        }

        // GameConfig.d.ts
        // 对EquipConfig_Flash和ThingConfig_Flash进行融合
        const structs = await JsonStructParser.Instance.readStructs(this.structPath, true);
        if (toolchain.projCfg.xml2ts?.diffThingEquip != 'YES') {
            JsonStructParser.Instance.mergeStructs('EquipConfigM', 'ThingConfigM');
        }
        await this.convert(structs);

        // KeyWord.ts
        let macros = JsonStructParser.Instance.getMacrosOfFile('KeyWord.xml');
        await this.convertKeyWord(macros);

        // 提交svn
        if(!toolchain.options.debug) {
            console.log("Committing...");
            await SvnHolder.Instance.commitSVN('xml2ts@fy-convertor~', this.gameConfigPath, this.keywordPath);

            // 同步
            if (toolchain.projCfg.Sync) {
                for (let i = 0, len = toolchain.projCfg.Sync.length; i < len; i++) {
                    console.log(`Syncing to ${toolchain.projCfg.Sync[i]}...`);
                    const gp = this.gameConfigPathsToSync[i];
                    const kp = this.keywordPathsToSync[i];
                    await fs.copyFile(path.join(this.gameConfigPath, 'GameConfig.d.ts'), path.join(gp, 'GameConfig.d.ts'));
                    await fs.copyFile(path.join(this.keywordPath, 'KeyWord.ts'), path.join(kp, 'KeyWord.ts'));
                    await fs.copyFile(path.join(this.keywordPath, 'KeyWord.d.ts'), path.join(kp, 'KeyWord.d.ts'));
                    await SvnHolder.Instance.commitSVN('xml2ts@fy-convertor~', gp, kp);
                }
            }
        } else {
            console.log("Debug mode, committing skipped.");
        }
    }

    private async initClientSVNs(repository: string, gameConfigPath: string, keywordPath: string): Promise<void> {
        let gcRoot: string, kwRoot: string;
        if (toolchain.projCfg.Project.ProjectType === 'H5ts') {
            gcRoot = adjustSVN(repository) + (toolchain.projCfg.xml2ts?.gameConfigRoot?? 'project/src/automatic/cfgs');
            kwRoot = adjustSVN(repository) + (toolchain.projCfg.xml2ts?.keyWordRoot ?? 'project/src/automatic/constants');
        } else {
            gcRoot = adjustSVN(repository) + (toolchain.projCfg.xml2ts?.gameConfigRoot?? 'project/TsScripts/System/data');
            kwRoot = adjustSVN(repository) + (toolchain.projCfg.xml2ts?.keyWordRoot ?? 'project/TsScripts/System/constants');
        }
        await SvnHolder.Instance.checkSVN(true, gameConfigPath, `${gcRoot}/GameConfig.d.ts`);
        await SvnHolder.Instance.checkSVN(true, keywordPath, kwRoot);
    }

    private async convert(structs: string[]): Promise<void> {
        const rootStructs: string[] = [];
        for(let s of structs) {
            const def = JsonStructParser.Instance.getStructDef(s);
            if (def && def._attributes.rootflag == '1') {
                rootStructs.push(s);
            }
        }
        const tnames = rootStructs.map(v => `'${v}'`).join(' | ');
        const ncms = rootStructs.map(v => `        ['${v}']: ${v};`).join('\n');

        let content = `declare module GameConfig {
    export type _TNames = ${tnames};
    export interface _NameToConfigMappings {
        [name: _TNames]: object;
${ncms}
    }
    export type ThingOrEquip = ThingConfigM | EquipConfigM;
`;
        const dfRules = toolchain.projCfg.xml2json?.dropFieldRules;
        const dfMap: Record<string, JSONDropFieldRule> = {};
        if (dfRules) {
            for (const rule of dfRules) {
                dfMap[rule.name] = rule;
            }
        }

        const dvRules = toolchain.projCfg.xml2json?.diffValueRules;
        const diffMap: Record<string, JsonDiffValueRule> = {};
        if (dvRules) {
            for (const rule of dvRules) {
                diffMap[rule.name] = rule;
            }
        }

        const descGenerator = (s: StructDef, e: EntryDef) => {
            const comments: string[] = [];
            const dfRule = dfMap[s._attributes.name];
            if (dfRule) {
                const dk = dfRule.dropKeys.find((v) => v.name == e._attributes.name);
                if (dk) {
                    const s = e._attributes.count || e._attributes.refer ? '某元素' : '';
                    if (dk.tests) {
                        const testStr = dk.tests.map((v) => `{@link ${e._attributes.type.replace('_Flash', 'M')}.${v.key}}` + (v.hitValue != null ? `==${v.hitValue}` : '为空')).join(', ');
                        comments.push(`@dropFieldRule 当${s}满足以下条件之一时将被丢弃：${testStr}`);
                    } else {
                        comments.push(`@dropFieldRule 当${s}为空时将被丢弃`);
                    }
                }
            }
            const diffRule = diffMap[s._attributes.name];
            if (diffRule) {
                const df = diffRule.diff_fields.find((v) => v.name == e._attributes.name);
                if (df) {
                    let s = '';
                    if (diffRule.serial_keys) {
                        s = `按照${diffRule.serial_keys.map((v) => `{@link ${v}}`).join(', ')}进行分组，然后`;
                    }
                    comments.push(`@diffValueRule 先${s}按照${diffRule.serial_sort_keys.map((v) => `{@link ${v}}`).join(', ')}进行排序，再对本字段的${df.key ? df.key : '值'}进行diff处理`);
                }
            }
            return comments;
        }
        for(let s of structs) {
            let structDef = JsonStructParser.Instance.makeStructCode(s, 'interface', descGenerator);
            content += indent(structDef);
        }
        content += '}';
        await fs.writeFile(path.join(this.gameConfigPath, 'GameConfig.d.ts'), content, 'utf-8');
    }

    private async convertKeyWord(macros: MacrosDef[]): Promise<void> {
        const groupTypeMap: { [gname: string]: TGroupInfo } = {};
        let kwContent: string = '';
        let descContent: string = '';
        let declareContent: string = '';
        let groups: string[] = [];
        for(let m of macros) {
            if(m._attributes.group) {
                let gt = groupTypeMap[m._attributes.group];
                if (!gt) {
                    groupTypeMap[m._attributes.group] = gt = { name: m._attributes.group, members: [] };
                }
                gt.members.push(m._attributes.name);

                let comment = m._attributes.value + ', group = {@link ' + m._attributes.group + '}';
                if (m._attributes.cname) {
                    comment += ', cname = ' + m._attributes.cname;
                }
                if (m._attributes.desc) {
                    if(m._attributes._convert_desc) {
                        comment += ', getDesc = ' + m._attributes.desc;
                        if(!groups.includes(m._attributes.group)) {
                            groups.push(m._attributes.group);
                            descContent += `\n            KWDesc.m[KeyWord.${m._attributes.group}] = {};\n`;
                        }
                        descContent += `            KWDesc.m[KeyWord.${m._attributes.group}][KeyWord.${m._attributes.name}] = "${m._attributes.desc}";\n`;
                    } else {
                        if (m._attributes.desc != m._attributes.cname) {
                            comment += ', desc = ' + m._attributes.desc;
                        }
                    }
                }
                kwContent += `    /**${comment}*/\n`;
                kwContent += `    ${m._attributes.name} = ${m._attributes.value},\n`;

                declareContent += `/**${comment}*/\n`;
                declareContent += `export type ${m._attributes.name} = ${m._attributes.value};\n`;
            } else {
                console.error("KeyWord should have a group attribute defined:", m._attributes.name);
            }
        }

        let groupTypeContent = '';
        for (const gname in groupTypeMap) {
            const gt = groupTypeMap[gname];
            groupTypeContent += `export type ${gname} = ` + gt.members.map((v) => `KeyWord.${v}`).join(' | ') + '\n';
        }

        declareContent += '\n\n\n' + groupTypeContent + '\n';

        // 添加group信息
        groups.sort();
        let groupContent: string = '';
        for(let i = 0, len = groups.length; i < len; i++) {
            let g = groups[i];
            groupContent += `    ${g} = ${i + 1},\n`;
        }

        const content = `/**表格使用的关键字，由fy-convertor根据KeyWord.xml生成*/
export const enum KeyWord {
// --------------------------- Keyword Groups ------------------------------
${groupContent}
// ------------------------------ Keywords ---------------------------------
${kwContent}
}

export class KWDesc {
    static m: { [group: number]: { [keyword: number]: string } };

    static getDesc(group: number, keyword: number): string {
        if (KWDesc.m == null) {
            KWDesc.m = {};
${descContent}
        }
        let desc: string = null;
        let groupDict = KWDesc.m[group];
        if(null != groupDict) {
            desc = groupDict[keyword];
        }
        return desc ? desc : "";
    }
}

${groupTypeContent}`;

        await fs.writeFile(path.join(this.keywordPath, 'KeyWord.ts'), content, 'utf-8');

        // 生成KeyWord.d.ts
        const kwdContent = `/**表格使用的关键字声明，由fy-convertor根据KeyWord.xml生成*/
declare namespace KeyWord {
${indent(declareContent, '    ')} 
}`;
        await fs.writeFile(path.join(this.keywordPath, 'KeyWord.d.ts'), kwdContent, 'utf-8');
    }
}